// Author: wsfuyibing <682805@qq.com>
// Date: 2025-05-01

package withdrawal

import (
	"context"
	"gitee.com/go-libs/db-xorm/db"
	"gitee.com/go-libs/log"
	"gitee.com/go-libs/result"
	"gitee.com/go-libs/validator-iris/validator"
	"gitee.com/go-wares/framework-iris/framework/src/logics"
	"github.com/kataras/iris/v12"
	"server/app/config"
	"server/app/errs"
	"server/app/http/models"
	"server/app/http/services"
	"server/app/kind"
	"server/app/lib/authorizer"
)

// Logic
// 余额账单.
type Logic struct {
	auth *authorizer.Authentication

	oauth         *models.Oauth
	userStatistic *models.UserStatistic
	withdrawal    *models.BalanceWithdrawal

	afterOperate, beforeOperate kind.Balance

	request  *Request
	response *Response
}

// New
// 创建逻辑实例.
func New() logics.Logical {
	return &Logic{
		request:  &Request{},
		response: &Response{},
	}
}

// Before
// 前置校验.
func (o *Logic) Before(_ context.Context, i iris.Context) (err error) {
	if err = validator.Json(i, o.request); err == nil {
		o.auth = authorizer.New().Get(i)
	}
	return
}

// Run
// 执行逻辑.
func (o *Logic) Run(ctx context.Context, _ iris.Context) result.Result {
	var (
		err error
		res = result.New()
	)

	// 事务处理.
	if err = db.Transaction(ctx,
		o.RunOauth,
		o.RunStatistic,
		o.RunApply,
		o.RunFreezed,
		o.RunRecords,
	); err != nil {
		return res.WithError(err)
	}

	// 提现成功.
	o.userStatistic.Balance = o.userStatistic.Balance - o.request.Amount
	o.userStatistic.BalanceFreezed = o.userStatistic.BalanceFreezed + o.request.Amount

	o.response.With(o.userStatistic)
	return res.With(o.response)
}

// RunOauth
// 校验授权.
func (o *Logic) RunOauth(ctx context.Context, sess *db.Session) (err error) {
	var has bool
	if o.oauth, has, err = services.NewOauthService(sess).Dao.GetByMap(ctx, map[string]any{
		"kind":    kind.OauthKindWeixin,
		"app_id":  config.WeiXin.GetAppId(),
		"user_id": o.auth.UserId,
	}); err != nil {
		err = errs.ErrDatabaseQuery
	} else if !has {
		err = errs.ErrWithdrawalNoWeixinOauth
	}
	return
}

// RunStatistic
// 校验金额.
func (o *Logic) RunStatistic(ctx context.Context, sess *db.Session) (err error) {
	var has bool

	// 1. 查询统计.
	if o.userStatistic, has, err = services.NewUserStatisticService(sess).Dao.GetBy(ctx, "user_id", o.auth.UserId); err != nil {
		err = errs.ErrDatabaseQuery
		return
	}

	// 2. 无效用户.
	if !has {
		err = errs.ErrUserNotFound
		return
	}

	// 3. 最大金额.
	//    提现金额不能超过可用余额.
	if o.request.Amount > o.userStatistic.Balance {
		err = errs.ErrWithdrawalGreaterThanAvailableBalance
		return
	}

	// 4. 记录结果.
	o.beforeOperate = o.userStatistic.Balance
	o.afterOperate = o.beforeOperate - o.request.Amount
	return
}

// RunApply
// 添加申请.
func (o *Logic) RunApply(ctx context.Context, sess *db.Session) (err error) {
	if o.withdrawal, err = services.NewBalanceWithdrawalService(sess).Dao.AddByStruct(ctx, &models.BalanceWithdrawal{
		UserId:  o.userStatistic.UserId,
		OauthId: o.oauth.Id,
		State:   kind.WithdrawalStateApplied,
		Amount:  o.request.Amount,
	}); err != nil {
		err = errs.ErrDatabaseQuery
	} else {
		log.Infofc(ctx, `[withdrawal] add new apply: record-id="%d"`, o.withdrawal.Id)
	}
	return
}

// RunFreezed
// 冻结余额.
//
//  1. 可用金额 减 提现额.
//  2. 冻结金额 增 提现额.
func (o *Logic) RunFreezed(ctx context.Context, sess *db.Session) (err error) {
	var affects int64
	if affects, err = sess.Cols("balance", "balance_freezed").
		Incr("balance", -o.request.Amount).
		Incr("balance_freezed", o.request.Amount).
		Where("`user_id` = ?", o.userStatistic.UserId).
		Update(o.userStatistic); err != nil {
		err = errs.ErrDatabaseQuery
	} else if affects > 0 {
		log.Infofc(ctx, `[withdrawal] balance freezed`)
	}
	return
}

// RunRecords
// 添加流水.
func (o *Logic) RunRecords(ctx context.Context, sess *db.Session) (err error) {
	var model *models.BalanceRecords
	if model, err = services.NewBalanceRecordsService(sess).Dao.AddByStruct(ctx, &models.BalanceRecords{
		UserId:        o.userStatistic.UserId,
		Before:        o.beforeOperate,               // 123.45
		Operate:       o.request.Amount.Reverse(),    // -100.00
		Balance:       o.afterOperate,                // 23.45
		OperateType:   kind.BalanceOperateWithdrawal, // 提现
		OperateIncome: kind.BalanceIncomeNo,          // 支出
		// OperateParams: fmt.Sprintf(`{"withdrawal_id": %d}`, o.withdrawal.Id),
	}); err != nil {
		err = errs.ErrDatabaseQuery
	} else {
		log.Infofc(ctx, `[withdrawal] add new record: record-id="%d", balance="%v", operate="%v", before="%v"`, model.Id, model.Balance, model.Operate, model.Before)
	}
	return
}
